/*
Copyright (C) Ariff Ehsan, Abdul Hadi Fikri

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

/*  Author : Ariff Ehsan
 *  Date   : Jan 12, 2010
 */


#include <system.h>
#include <lcd162/lcd162.h>
#include <util/delay.h>


//#define LCD162_WRITE() CLR_BIT(LCD162_CTRL_PORT, LCD162_RW_PIN)
//#define LCD162_READ() SET_BIT(LCD162_CTRL_PORT, LCD162_RW_PIN)

//#define LCD162_CMD() CLR_BIT(LCD162_CTRL_PORT, LCD162_RS_PIN)
//#define LCD162_DATA() SET_BIT(LCD162_CTRL_PORT, LCD162_RS_PIN)


enum lcd162_enums {
	
	LCD162_STATE_STOP = 0,
	LCD162_STATE_STARTING,
	
	LCD162_STATE_RESET1,
	LCD162_STATE_RESET2,
	LCD162_STATE_RESET3,
	
	LCD162_STATE_STARTED,
};


/**
 *  Local prototype functions
 */
void LCD162_TIMER_run(void);

void LCD162_TASKWAIT_run(void);
void LCD162_TASK_run(void);

void LCD162_defaultStartDone(error_t);

void LCD162_writeCmd(uint8_t);
void LCD162_epulse(void);


struct {
	
	uint8_t rs;
	uint8_t data;
	
} lcd162_buff[LCD162_MAX_BUFF];


/**
 *  Local variables
 */
timer_t lcd162_timer;

task_t lcd162_taskWait;
task_t lcd162_task;

void (*lcd162_startdone)(error_t err);

BOOL    lcd162_started = FALSE;
uint8_t lcd162_state   = LCD162_STATE_STOP;

uint8_t lcd162_rstCount;

uint8_t lcd162_in,   lcd162_out;
BOOL    lcd162_full, lcd162_busy;


/******************************************************************************
 *                           FUNCTION IMPLEMENTATION
 */

void LCD162_reg(void (*startdone)(error_t err)) {

	if (startdone != NULL)
		lcd162_startdone = startdone;
	else
		lcd162_startdone = LCD162_defaultStartDone;
}


error_t LCD162_start(void) {
	
	if (lcd162_state != LCD162_STATE_STOP)
		return FAIL;
	
	lcd162_state = LCD162_STATE_STARTING;
	
	lcd162_in = lcd162_out = 0;
	lcd162_full = lcd162_busy = FALSE;
	
	TIMER_reg(&lcd162_timer, LCD162_TIMER_run);
	
	TASK_reg(&lcd162_task, LCD162_TASK_run);
	TASK_reg(&lcd162_taskWait, LCD162_TASKWAIT_run);
	
	/* initalize lcd data pins */
	LCD162_DDR = 0xff;
	LCD162_PORT = 0;

	/* initialize lcd control pins */
	LCD162_CTRL_DDR |= (1<<LCD162_E_PIN)|(1<<LCD162_RW_PIN)|(1<<LCD162_RS_PIN);

	/* set e pin to low logic */
	LCD162_CTRL_PORT &= ~(1<<LCD162_E_PIN);

	/* delay before using lcd */
	TIMER_start(&lcd162_timer, 100);
	
	return SUCCESS;
}


void LCD162_TIMER_run(void) {
	
	switch (lcd162_state) {
	
	case LCD162_STATE_STARTING:
		
		LCD162_PORT = 0x30;
		LCD162_epulse();
		
		lcd162_state = LCD162_STATE_RESET1;
		TIMER_start(&lcd162_timer, 10);
		
		break;
		
	case LCD162_STATE_RESET1:
		
		LCD162_PORT = 0x30;
		LCD162_epulse();
		
		lcd162_state = LCD162_STATE_RESET2;
		TIMER_start(&lcd162_timer, 10);
		
		break;
		
	case LCD162_STATE_RESET2:
		
		LCD162_PORT = 0x30;
		LCD162_epulse();
		
		lcd162_state = LCD162_STATE_RESET3;
		TIMER_start(&lcd162_timer, 10);
		
		break;
		
	case LCD162_STATE_RESET3:
		
		/* start configure lcd */
		
		LCD162_writeCmd(0x08);
		LCD162_writeCmd(0x38);
		LCD162_writeCmd(0x0d);
		LCD162_writeCmd(0x01);
		LCD162_writeCmd(0x06);
		
		lcd162_state = LCD162_STATE_STARTED;
		
		lcd162_startdone(SUCCESS);
		
		break;
	}
}


void LCD162_TASKWAIT_run(void) {
	
	/* set data pins as input */
	LCD162_DDR = 0;

	/* set RW(read mode) and set E pin */
	LCD162_CTRL_PORT |= (1<<LCD162_RW_PIN)|(1<<LCD162_E_PIN);

	asm volatile ("nop");
	asm volatile ("nop");

	uint8_t d = LCD162_PIN;

	LCD162_CTRL_PORT &= ~((1<<LCD162_RW_PIN)|(1<<LCD162_E_PIN));
	
	uint8_t i = d & 0x80;

	if (i == 0x80)
		TASK_post(&lcd162_taskWait);
	else {
		
		lcd162_out = (lcd162_out + 1) % LCD162_MAX_BUFF;
		lcd162_full = FALSE;
		
		TASK_post(&lcd162_task);
	}
}


void LCD162_TASK_run(void) {
	
	if (!lcd162_full && lcd162_in == lcd162_out) {
		
		lcd162_busy = TRUE;
		return;
	}
	
	if (lcd162_buff[lcd162_out].rs == 0)
		LCD162_CTRL_PORT &= ~(1<<LCD162_RS_PIN);
	else
		LCD162_CTRL_PORT |= (1<<LCD162_RS_PIN);
	
	LCD162_DDR = 0xff;
	LCD162_PORT = lcd162_buff[lcd162_out].data;
	
	LCD162_epulse();
	
	TASK_post(&lcd162_taskWait);
}


void LCD162_writeCmd(uint8_t cmd) {
	
	if (lcd162_full)
		return;
	
	lcd162_buff[lcd162_in].rs = 0;
	lcd162_buff[lcd162_in].data = cmd;
	
	lcd162_in = (lcd162_in + 1) % LCD162_MAX_BUFF;
	
	if (lcd162_in == lcd162_out)
		lcd162_full = TRUE;
	
	if (!lcd162_busy) {
	
		lcd162_busy = TRUE;
		TASK_post(&lcd162_task);
	}
}


void LCD162_epulse(void) {
	
	LCD162_CTRL_PORT |= (1<<LCD162_E_PIN);
	
	asm volatile ("nop");
	asm volatile ("nop");

	LCD162_CTRL_PORT &= ~(1<<LCD162_E_PIN);
}


void LCD162_putChar(uint8_t chr) {
	
	if (lcd162_full)
		return;
	
	lcd162_buff[lcd162_in].rs = 1;
	lcd162_buff[lcd162_in].data = chr;
	
	lcd162_in = (lcd162_in + 1) % LCD162_MAX_BUFF;
	
	if (lcd162_in == lcd162_out)
		lcd162_full = TRUE;
	
	if (!lcd162_busy) {
	
		lcd162_busy = TRUE;
		TASK_post(&lcd162_task);
	}
}


void LCD162_putString(uint8_t* str, uint8_t len) {
	
	uint8_t i;
	
	for (i = 0; i < len; i++)
		LCD162_putChar(str[i]);
}


void LCD162_defaultStartDone(error_t err) {
}

